/*
 * License: Copyright (c) Huawei Technologies Co., Ltd. 2012-2019. All rights reserved.
 * Description: video decode
 * Date: 2020-01-07 09:50:36
 * LastEditTime : 2020-02-19 09:49:51
 */


#include "hiaiengine/ai_memory.h"
#include "hiaiengine/log.h"
#include "vdec_engine.h"

const int D1_WIDTH = 720;
const int D1_HEIGHT = 576;
const int CIF_WIDTH = 352;
const int CIF_HEIGHT = 288;
const int VPC_WIDTH_ALIGN = 16;
const int VPC_HEIGHT_ALIGN = 2;
const int TIMES_MICROSECOND = 1000000;
uint64_t VdecEngine::decodeFrameNum = 0;
uint64_t VdecEngine::GetCurentTimeStamp()
{
    struct timeval curTime;
    gettimeofday(&curTime, NULL);
    return curTime.tv_sec * TIMES_MICROSECOND + curTime.tv_usec;
}

void VdecEngine::VdecResultCallback(FRAME* frame, void* hiaiData)
{
    /* judge parameter */
    if (hiaiData == nullptr) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, " input parameter of VdecResultCallback error!");
        return;
    }

    /* Get decode video information */
    DecodeH26xInfo* info = (DecodeH26xInfo*)hiaiData;
    VdecEngine* enginePtr = (VdecEngine*)info->vdecEnginePtr;

    /* convert hbfc to yuv420 */
    std::shared_ptr<FRAME_BUFFER> frameBuf = std::make_shared<FRAME_BUFFER>();
    frameBuf->frameId = info->frameId;
    if (enginePtr->VdecImageResize(frame, frameBuf, info->resizeWidth, info->resizeHeight) != HIAI_OK) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, " VdecImageResize of VdecResultCallback error!");
        return;
    }
    VdecEngine::decodeFrameNum += 1;
    uint64_t timeDiff = enginePtr->GetCurentTimeStamp() - enginePtr->initTimeStamp;
    if (timeDiff == 0) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "Get time error.");
        return;
    }
    frameBuf->decodeFps = VdecEngine::decodeFrameNum / (1.0 * timeDiff / TIMES_MICROSECOND);
    enginePtr->frameQueue.push(frameBuf);
    return;
}

void VdecEngine::VdecErrorCallback(VDECERR* vdecErr)
{
    HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "VdecErrorCallback vdec error type=%d", vdecErr->errType);
    VdecEngine::decodeFrameNum += 1;
}

HIAI_StatusT VdecEngine::Init(const AIConfig& config, const std::vector<AIModelDescription>& model_desc)
{
    HIAI_StatusT ret = HIAI_OK;
    /* create the vdec object */
    if (piDvppApiVdec == NULL) {
        ret = CreateVdecApi(piDvppApiVdec, 0);
        if ((ret != 0) || (piDvppApiVdec == NULL)) {
            HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "fail to intialize dvpp api!");
            return HIAI_ERROR;
        }
    }

    /* create the vpc object */
    if (piDvppApiVpc == NULL) {
        ret = CreateDvppApi(piDvppApiVpc);
        if ((ret != 0) || (piDvppApiVpc == NULL)) {
            HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "fail to intialize vpc api!");
            return HIAI_ERROR;
        }
    }

    return HIAI_OK;
}

VdecEngine::~VdecEngine()
{
    /* Destroy vdec object */
    if (piDvppApiVdec != NULL) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "Destroy vdec api!");
        DestroyVdecApi(piDvppApiVdec, 0);
        piDvppApiVdec = NULL;
    }

    /* Destroy vpc object */
    if (piDvppApiVpc != NULL) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "Destroy vpc api!");
        DestroyDvppApi(piDvppApiVpc);
        piDvppApiVpc = NULL;
    }
}

HIAI_StatusT VdecEngine::TransferData(std::shared_ptr<FRAME_BUFFER> frameTemp, std::shared_ptr<TransData> config)
{
    /* construct data transmission struct */
    std::shared_ptr<TransData> output = std::make_shared<TransData>();
    output->graphId = config->graphId;
    output->deviceId = config->deviceId;
    output->channelId = config->channelId;
    output->outputFormat = config->outputFormat;
    output->vencFrameRate = config->vencFrameRate;
    output->vencBitRate = config->vencBitRate;
    output->vencCtlType = config->vencCtlType;
    output->videoFormat = config->videoFormat;
    output->eos = this->eos;
    output->streamPullFps = config->streamPullFps;
    if (frameTemp.get() != nullptr && this->eos == 0) {
        output->frameId = frameTemp->frameId;
        output->curDecodeFps = frameTemp->decodeFps;
        output->width = frameTemp->width;
        output->height = frameTemp->height;
        output->alignWidth = frameTemp->alignWidth;
        output->alignHeight = frameTemp->alignHeight;
        output->buf = frameTemp->buf;
        output->len = frameTemp->len;
    } else {
        output->len = 0;
    }

    HIAI_StatusT ret = hiai::Engine::SendData(0, "TransData", std::static_pointer_cast<void>(output));
    if (ret != HIAI_OK) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, " SendData of VdecEngine error! ErrCode: %d", ret);
    }
    return ret;
}

/**
 * @ingroup hiaiengine
 *
 **/
HIAI_IMPL_ENGINE_PROCESS("VdecEngine", VdecEngine, INPUT_SIZE)
{
    std::shared_ptr<TransData> input = std::static_pointer_cast<TransData>(arg0);
    if (input == nullptr) {
        return HIAI_ERROR;
    }

    if (this->initTimeStamp == 0) {
        this->initTimeStamp = this->GetCurentTimeStamp();
    }

    if (input->eos == 1) {
        this->eos = 1;
        return this->TransferData(nullptr, input);
    }

    if (DecodeH26xVideo(input) != HIAI_OK) {
        return HIAI_ERROR;
    }

    while (!frameQueue.empty()) {
        std::shared_ptr<FRAME_BUFFER> frameTemp = frameQueue.front();
        HIAI_StatusT ret = this->TransferData(frameTemp, input);
        if (ret != HIAI_OK) {
            return ret;
        }
        frameQueue.pop();
    }

    return HIAI_OK;
}

HIAI_StatusT VdecEngine::DecodeH26xVideo(std::shared_ptr<TransData> streamRawData)
{
    /* define parameters for DVPP Vdec */
    vdec_in_msg vdecMsg;

    /* decode the H26x video */
    const int FORMAT_LEN = 4;
    int ret = 0;
    if (streamRawData->videoFormat == 0) {
        ret = memcpy_s(vdecMsg.video_format, FORMAT_LEN, "h264", FORMAT_LEN);
    } else {
        ret = memcpy_s(vdecMsg.video_format, FORMAT_LEN, "h265", FORMAT_LEN);
    }
    if (ret != 0) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "memory copy failed! ErrCode:%d", ret);
        return HIAI_ERROR;
    }
    vdecMsg.in_buffer = (char*)streamRawData->buf.get();
    vdecMsg.in_buffer_size = streamRawData->len;

    std::shared_ptr<DecodeH26xInfo> decodeH26xInfo = make_shared<DecodeH26xInfo>();
    decodeH26xInfo->setFrameIndex(streamRawData->frameId);
    decodeH26xInfo->frameId = streamRawData->frameId;
    int width = 0;
    int height = 0;
    if (GetOutputSize(width, height, streamRawData->outputFormat) != HIAI_OK) {
        return HIAI_ERROR;
    }
    decodeH26xInfo->resizeWidth = width;
    decodeH26xInfo->resizeHeight = height;
    decodeH26xInfo->vdecEnginePtr = (void*)this;
    vdecMsg.hiai_data_sp = decodeH26xInfo;  // if use hiai_data_sp, set hiai_data as NULL
    vdecMsg.channelId = 0;
    vdecMsg.hiai_data = NULL;  // if use hiai_data_sp, set hiai_data as NULL
    vdecMsg.call_back = VdecEngine::VdecResultCallback;
    vdecMsg.err_report = VdecEngine::VdecErrorCallback;

    dvppapi_ctl_msg dvppapiCtlMsg;
    dvppapiCtlMsg.in_size = sizeof(vdec_in_msg);
    dvppapiCtlMsg.in = (void*)&vdecMsg;
    ret = VdecCtl(piDvppApiVdec, DVPP_CTL_VDEC_PROC, &dvppapiCtlMsg, 0);
    if (ret != 0) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, " VdecCtl failed! ErrCode: %d", ret);
        return HIAI_ERROR;
    }
    return HIAI_OK;
}

HIAI_StatusT VdecEngine::GetOutputSize(int& width, int& height, int format)
{
    if (format == D1) {
        width = D1_WIDTH;
        height = D1_HEIGHT;
    } else if (format == CIF) {
        width = CIF_WIDTH;
        height = CIF_HEIGHT;
    } else {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, " Invalid outputFormat, please check!");
        return HIAI_ERROR;
    }
    return HIAI_OK;
}

HIAI_StatusT VdecEngine::VdecImageResize(
    FRAME* frame, std::shared_ptr<FRAME_BUFFER> frameBuf, int resizeWidth, int resizeHeight)
{
    /* Construct VPC input parameter */
    std::shared_ptr<VpcUserImageConfigure> imageConfigure(new VpcUserImageConfigure);
    imageConfigure->bareDataAddr = nullptr;
    imageConfigure->bareDataBufferSize = 0;
    // set input frame format
    imageConfigure->isCompressData = true;
    imageConfigure->widthStride = frame->width;
    imageConfigure->heightStride = frame->height;
    string imageFormat(frame->image_format);
    if (frame->bitdepth == 8) {
        if (imageFormat == "nv12") {
            imageConfigure->inputFormat = INPUT_YUV420_SEMI_PLANNER_UV;
        } else {
            imageConfigure->inputFormat = INPUT_YUV420_SEMI_PLANNER_VU;
        }
    } else {
        if (imageFormat == "nv12") {
            imageConfigure->inputFormat = INPUT_YUV420_SEMI_PLANNER_UV_10BIT;
        } else {
            imageConfigure->inputFormat = INPUT_YUV420_SEMI_PLANNER_VU_10BIT;
        }
    }
    // set hfbc input address
    VpcCompressDataConfigure* compressDataConfigure = &imageConfigure->compressDataConfigure;
    uintptr_t baseAddr = (uintptr_t)frame->buffer;
    compressDataConfigure->lumaHeadAddr = baseAddr + frame->offset_head_y;
    compressDataConfigure->chromaHeadAddr = baseAddr + frame->offset_head_c;
    compressDataConfigure->lumaPayloadAddr = baseAddr + frame->offset_payload_y;
    compressDataConfigure->chromaPayloadAddr = baseAddr + frame->offset_payload_c;
    compressDataConfigure->lumaHeadStride = frame->stride_head;
    compressDataConfigure->chromaHeadStride = frame->stride_head;
    compressDataConfigure->lumaPayloadStride = frame->stride_payload;
    compressDataConfigure->chromaPayloadStride = frame->stride_payload;

    imageConfigure->outputFormat = OUTPUT_YUV420SP_UV;
    imageConfigure->yuvSumEnable = false;
    imageConfigure->cmdListBufferAddr = nullptr;
    imageConfigure->cmdListBufferSize = 0;

    /* ouput 0: convert format from INPUT_YUV420_SEMI_PLANNER_VU to OUTPUT_YUV420SP_UV */
    std::shared_ptr<VpcUserRoiConfigure> roiConfigureOutput(new VpcUserRoiConfigure);
    imageConfigure->roiConfigure = roiConfigureOutput.get();
    roiConfigureOutput->next = nullptr;

    VpcUserRoiInputConfigure* inputConfigure = &roiConfigureOutput->inputConfigure;
    // set map area: the entire picture
    inputConfigure->cropArea.leftOffset = 0;                                                          // even
    inputConfigure->cropArea.rightOffset = (frame->width % 2) ? frame->width : (frame->width - 1);    // odd
    inputConfigure->cropArea.upOffset = 0;                                                            // even
    inputConfigure->cropArea.downOffset = (frame->height % 2) ? frame->height : (frame->height - 1);  // odd

    // Construct output buffer
    VpcUserRoiOutputConfigure* outputConfig = &roiConfigureOutput->outputConfigure;
    outputConfig->widthStride = ALIGN_UP(resizeWidth, VPC_WIDTH_ALIGN);                         // align to 16
    outputConfig->heightStride = ALIGN_UP(resizeHeight, VPC_HEIGHT_ALIGN);                      // align to 2
    outputConfig->bufferSize = outputConfig->widthStride * outputConfig->heightStride * 3 / 2;  // yuv420sp_UV
    // Construct output buffer
    uint8_t* outputBuffer = nullptr;
    HIAI_StatusT ret = hiai::HIAIMemory::HIAI_DVPP_DMalloc(outputConfig->bufferSize, (void*&)outputBuffer);
    if (ret != HIAI_OK || outputBuffer == nullptr) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, "DVPP_DMalloc error! ErrCode: %d", ret);
        return HIAI_ERROR;
    }
    outputConfig->addr = outputBuffer;  // align to 128

    // set crop area:
    outputConfig->outputArea.leftOffset = 0;  // 这个偏移值需要16对齐
    outputConfig->outputArea.rightOffset = (resizeWidth % 2) ? resizeWidth : (resizeWidth - 1);
    outputConfig->outputArea.upOffset = 0;
    outputConfig->outputArea.downOffset = (resizeHeight % 2) ? resizeHeight : (resizeHeight - 1);

    /* process of VPC */
    dvppapi_ctl_msg dvppApiCtlMsg;
    dvppApiCtlMsg.in = static_cast<void*>(imageConfigure.get());
    dvppApiCtlMsg.in_size = sizeof(VpcUserImageConfigure);
    ret = DvppCtl(piDvppApiVpc, DVPP_CTL_VPC_PROC, &dvppApiCtlMsg);
    if (ret != 0) {
        HIAI_ENGINE_LOG(HIAI_IDE_ERROR, " vpc failed! ErrCode: %d", ret);
        hiai::HIAIMemory::HIAI_DVPP_DFree(roiConfigureOutput->outputConfigure.addr);
        return HIAI_ERROR;
    }

    /* Get output image */
    frameBuf->width = imageConfigure->roiConfigure->outputConfigure.outputArea.rightOffset;
    frameBuf->height = imageConfigure->roiConfigure->outputConfigure.outputArea.downOffset;
    frameBuf->alignWidth = imageConfigure->roiConfigure->outputConfigure.widthStride;
    frameBuf->alignHeight = imageConfigure->roiConfigure->outputConfigure.heightStride;
    frameBuf->buf = std::shared_ptr<uint8_t>(imageConfigure->roiConfigure->outputConfigure.addr,
        [](std::uint8_t* data) { hiai::HIAIMemory::HIAI_DVPP_DFree(data); });
    frameBuf->len = imageConfigure->roiConfigure->outputConfigure.bufferSize;
    return HIAI_OK;
}
